package com.google.hackathon.geochallenge;

import android.app.Activity;
import android.content.Context;
import android.content.Intent;
import android.location.Location;
import android.location.LocationListener;
import android.location.LocationManager;
import android.os.Bundle;
import android.os.Parcelable;
import android.util.Log;
import android.util.Pair;
import android.view.Menu;
import android.view.MenuItem;
import android.view.View;
import android.widget.Button;
import android.widget.TextView;
import android.widget.Toast;

import com.google.android.gms.maps.CameraUpdateFactory;
import com.google.android.gms.maps.GoogleMap;
import com.google.android.gms.maps.MapView;
import com.google.android.gms.maps.MapsInitializer;
import com.google.android.gms.maps.model.LatLng;
import com.google.android.gms.maps.model.LatLngBounds;
import com.google.android.gms.maps.model.Polyline;
import com.google.android.gms.maps.model.PolylineOptions;

import java.util.ArrayList;
import java.util.Calendar;
import java.util.List;

public class RecordChallengeActivity extends Activity implements LocationListener {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_record_challenge_activity);

        mContinuouslyRecording = false;
        mLocationManager = (LocationManager) getSystemService(Context.LOCATION_SERVICE);
        mChallenge = new Challenge();

        mLocationManager.requestLocationUpdates(LocationManager.GPS_PROVIDER, 0, 0, this);

        MapsInitializer.initialize(this);
        mapView().onCreate(savedInstanceState);
        mapView().getMap().setMapType(GoogleMap.MAP_TYPE_HYBRID);

        if (savedInstanceState != null) {
            final Bundle instanceData = (savedInstanceState.containsKey(BUNDLE_NAME) ?
                    savedInstanceState.getBundle(BUNDLE_NAME) : null);
            if (instanceData == null) {
                Log.e(TAG, "savedInstanceState does not contain entry for key=" + BUNDLE_NAME);
            } else {
                setContinuouslyRecording(instanceData.getBoolean(M_CONTINUOUSLY_RECORDING_KEY));
                mChallenge = instanceData.getParcelable(M_CHALLENGE_KEY);
                mLastLocation = instanceData.getParcelable(M_LAST_LOCATION_KEY);
                mLastTimestamp = instanceData.getLong(M_LAST_TIMESTAMP_KEY);
                showMessage(instanceData.getCharSequence(STATUS_MSG_KEY));

                // reconstruct the polyline
                mCurPath = mapView().getMap().addPolyline(new PolylineOptions());
                List<LatLng> curPoints = mCurPath.getPoints();
                for (Checkpoint checkpoint : mChallenge.getCheckpoints()) {
                    curPoints.add(new LatLng(checkpoint.getLatitude(), checkpoint.getLongitude()));
                }
                mCurPath.setPoints(curPoints);
            }
        }
        Log.i(TAG, "done onCreate()");
    }

    @Override
    protected void onResume() {
        super.onResume();
        mapView().onResume();

        // zoom to the current location
        mapView().getMap().setMyLocationEnabled(true);
    }

    @Override
    protected void onPause() {
        super.onPause();
        mapView().onPause();
    }

    @Override
    protected void onDestroy() {
        super.onDestroy();
        mLocationManager.removeUpdates(this);

        mapView().onDestroy();
        Log.i(TAG, "done onDestroy()");
    }

    @Override
    protected void onSaveInstanceState(Bundle outState) {
        super.onSaveInstanceState(outState);
        mapView().onSaveInstanceState(outState);
        if (outState.containsKey(BUNDLE_NAME)) {
            Log.e(TAG, "outState Bundle already contains entry for key=" + BUNDLE_NAME);
            return;
        }

        Bundle instanceData = new Bundle();
        instanceData.putBoolean(M_CONTINUOUSLY_RECORDING_KEY, mContinuouslyRecording);
        instanceData.putParcelable(M_CHALLENGE_KEY, mChallenge);
        instanceData.putParcelable(M_LAST_LOCATION_KEY, mLastLocation);
        instanceData.putLong(M_LAST_TIMESTAMP_KEY, mLastTimestamp);
        instanceData.putCharSequence(STATUS_MSG_KEY, statusMessageTextView().getText());
        outState.putBundle(BUNDLE_NAME, instanceData);
    }

    @Override
    public void onLowMemory() {
        super.onLowMemory();
        mapView().onLowMemory();
    }

    @Override
    public boolean onCreateOptionsMenu(Menu menu) {
        // Inflate the menu; this adds items to the action bar if it is present.
        getMenuInflater().inflate(R.menu.geo_record_challenge, menu);
        return true;
    }

    @Override
    public boolean onOptionsItemSelected(MenuItem item) {
        // Handle action bar item clicks here. The action bar will
        // automatically handle clicks on the Home/Up button, so long
        // as you specify a parent activity in AndroidManifest.xml.
        int id = item.getItemId();
        if (id == R.id.action_settings) {
            return true;
        }
        return super.onOptionsItemSelected(item);
    }

    // LocationListener interface
    public void onProviderDisabled(String provider) {

    }

    public void onProviderEnabled(String provider) {

    }

    public void onStatusChanged(String provider, int status, Bundle extras) {

    }

    private TextView statusMessageTextView() {
        return (TextView) findViewById(R.id.textRecordStatus);
    }
    private void showMessage(CharSequence msg) {
        statusMessageTextView().setText(msg);
        Context context = getApplicationContext();
        Toast.makeText(context, msg, Toast.LENGTH_SHORT).show();
    }

    private boolean locationIsAdmissible(Location location, StringBuilder errorBuilder) {
        if (location == null) {
            errorBuilder.append("No location data available");
            return false;
        }

        if (location.getAccuracy() > 100.0) {
            errorBuilder.append("Location not sufficiently accurate (");
            errorBuilder.append(Double.toString(location.getAccuracy()));
            errorBuilder.append(")");
            return false;
        }

        if (mLastLocation != null) {
            if (location.distanceTo(mLastLocation) < 2.0) {
                errorBuilder.append("Location too close to previous point (");
                errorBuilder.append(Double.toString(location.distanceTo(mLastLocation)));
                errorBuilder.append("m)");
                return false;
            }
        }
        return true;
    }
    private void addLocation(Location location) {
        StringBuilder statusBuilder = new StringBuilder();
        if (locationIsAdmissible(location, statusBuilder)) {
            Checkpoint checkpoint = new Checkpoint(
                    location.getLatitude(),location.getLongitude(), location.getAccuracy());
            mChallenge.getCheckpoints().add(checkpoint);
            mLastLocation = location;
            renderPath(location);
            statusBuilder.append("Added point: (" + location.getLatitude() + ", " + location.getLongitude());
        }

        showMessage("[" + mChallenge.getCheckpoints().size() + " points] " + statusBuilder.toString());
    }

    private void renderPath(Location location) {
        if (mCurPath == null) {
            mCurPath = mapView().getMap().addPolyline(new PolylineOptions());
        }
        List<LatLng> curPoints = mCurPath.getPoints();
        curPoints.add(new LatLng(location.getLatitude(), location.getLongitude()));
        mCurPath.setPoints(curPoints);
    }
    private boolean minTimePassed(long curTimestamp) {
        return curTimestamp >= mLastTimestamp + 3000;  // 3s min elapsed time
    }

    public void onLocationChanged(Location location) {
        updateMapView(location);
        long curTimestamp = Calendar.getInstance().getTimeInMillis();
        if (mContinuouslyRecording && minTimePassed(curTimestamp)) {
            addLocation(location);
            mLastTimestamp = curTimestamp;
        }
    }

    public void onBackPressed() {
        Log.i(TAG, "onBackPressed");
        setResult(Activity.RESULT_CANCELED, new Intent());
        super.onBackPressed();
    }

    // button handlers
    public void doAddPointToChallenge(View view) {
        addLocation(mLocationManager.getLastKnownLocation(LocationManager.GPS_PROVIDER));
    }
    private void setContinuouslyRecording(boolean newValue) {
        mContinuouslyRecording = newValue;

        Button button = (Button) findViewById(R.id.buttonToggleRecordChallenge);
        if (mContinuouslyRecording) {
            button.setText(getString(R.string.button_stop_record));
        } else {
            button.setText(getString(R.string.button_start_record));
        }
    }
    public void doToggleRecordChallenge(View view) {
        setContinuouslyRecording(!mContinuouslyRecording);
    }

    public void doDoneRecordChallenge(View view) {
        Intent rv;
        rv = new Intent();
        rv.putExtra("challenge", mChallenge);
        setResult(Activity.RESULT_OK, rv);
        finish();
    }

    private MapView mapView() {
        return (MapView) findViewById(R.id.record_map);
    }

    private void updateMapView(Location location) {
        LatLng curLatLng = new LatLng(location.getLatitude(), location.getLongitude());
        LatLngBounds bounds = mChallenge.computeCameraView(curLatLng);
        mapView().getMap().moveCamera(CameraUpdateFactory.newLatLngBounds(bounds, 120));
    }
    // constants
    private static final String TAG = RecordChallengeActivity.class.getSimpleName();
    private static final String BUNDLE_NAME = TAG + "_INSTANCE_DATA";
    private static final String M_CONTINUOUSLY_RECORDING_KEY = "mContinuouslyRecording";
    private static final String M_CHALLENGE_KEY = "mChallenge";
    private static final String M_LAST_LOCATION_KEY = "mLastLocation";
    private static final String M_LAST_TIMESTAMP_KEY = "mLastTimestamp";
    private static final String STATUS_MSG_KEY = "_statusMsg";

    // private members
    private boolean mContinuouslyRecording;
    private LocationManager mLocationManager;
    private Challenge mChallenge;
    private Location mLastLocation;

    // GPS updates come fast and furious; rate-limit them
    private long mLastTimestamp;

    // current map polyline
    private Polyline mCurPath;
}
